﻿#region License
/*

	Copyright (C) 2008 Oliver Charles, Daniel Hollander

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
	
*/
#endregion

using System;
using System.Collections.Generic;
using Moebius.Components;
using Moebius.Core;

namespace Moebius.Components
{
    /// <summary>
    /// Provides the data for the event caused by the addition of an <see cref="Entity"/> to a <see cref="World"/>
    /// </summary>
    public sealed class EntityAddedEventArgs : EventArgs
    {
        #region Properties

        private Entity entity;

        /// <summary>
        /// Gets the <see cref="Entity"/> that was added to the <see cref="World"/>
        /// </summary>
        public Entity Entity
        {
            get { return entity; }
            set { entity = value; }
        }

        #endregion
    }

    /// <summary>
    /// Provides the data for the event caused by the removal of an <see cref="Entity"/> from a <see cref="World"/>
    /// </summary>
    public sealed class EntityRemovedEventArgs : EventArgs
    {
        #region Properties

        private Entity entity;

        /// <summary>
        /// Gots the <see cref="Entity"/> that has been removed from the <see cref="World"/>.
        /// </summary>
        public Entity Entity
        {
            get { return entity; }
            set { entity = value; }
        }

        #endregion
    }

    /// <summary>
    /// A world is a collection of <see cref="Entity"/>s that are all in some way current. The <see cref="World"/>
    /// also manages all component subsystems, required for Entity creation.
    /// 
    /// You should subclass <see cref="World"/> to create your own game specific Worlds, that keep track of important
    /// <see cref="Entity"/>s - such as the player.
    /// </summary>
    public class World : Task
    {
        #region Private Variables

        // A list of all entities in the world
        private readonly List<Entity> entities = new List<Entity>();

        // We maintain seperate lists of entities to add or remove to the scene, to prevent enumerators exploding.
        // ... Yes, that's the technical term
        readonly List<Entity> removedEntities = new List<Entity>();
        readonly List<Entity> addedEntities = new List<Entity>();

        // A list of all subsystems (component managers)
        readonly Dictionary<Type, ISubsystem> subsystems = new Dictionary<Type, ISubsystem>();

        private ResourceFactory resourceFactory;

        #endregion

        #region Properties

        public int EntityCount
        {
            get { return entities.Count; }
        }

        #endregion
        #region Events

        /// <summary>
        /// Occurs when an Entity is added to the World.
        /// </summary>
        public event EventHandler<EntityAddedEventArgs> EntityAdded;

        /// <summary>
        /// Occurs when an Entity is removed from the World.
        /// </summary>
        public event EventHandler<EntityRemovedEventArgs> EntityRemoved;

        #endregion

        #region Initialization

        /// <summary>
        /// Initialize the <see cref="World"/> with a specific <see cref="ResourceFactory"/>.
        /// </summary>
        /// <param name="factory">The <see cref="ResourceFactory"/> that <see cref="Entity"/>s may use to load resources</param>
        public virtual void Initialize(ResourceFactory factory)
        {
            /* TODO I don't like this. Can we give subsystems the resource factory instead, and let components be
             *       created with a pointer to this? It doesn't feel right giving the Resource Factory to the world, which is
             *       meant to be an *abstract* representation. */
            resourceFactory = factory;
        }

        #endregion

        #region Subsystem Management

        /// <summary>
        /// Register a component subsystem with this World.
        /// </summary>
        /// <remarks>
        /// Registering a subsystem binds the subsystem to the World, causing it to respond to entity addition
        /// and removal. It also allows Entitys to query there composition, and retrieve exact components.
        /// </remarks>
        /// <param name="componentType">The <see cref="Type"/> of Component that this subsystem will manage.</param>
        /// <param name="subsystem">An instance of the subsystem</param>
        public void RegisterSubsystem(Type componentType, ISubsystem subsystem)
        {
            subsystems.Add(componentType, subsystem);
            subsystem.Register(this);
        }

        #endregion
        #region Scene Manipulation

        /// <summary>
        /// Request an Entity to be added to the scene.
        /// </summary>
        /// <param name="entity">The Entity to add to the scene.</param>
        public void InjectEntity(Entity entity)
        {
            addedEntities.Add(entity);
        }

        /// <summary>
        /// Request an Entity to be removed from the scene.
        /// </summary>
        /// <param name="entity">The Entity to remove from the scene.</param>
        public void RemoveEntity(Entity entity)
        {
            removedEntities.Add(entity);
        }

        #endregion
        #region Scene Querying

        /// <summary>
        /// Find all Entitys of a specific type.
        /// </summary>
        /// <typeparam name="T">The <em>exact</em> type of the entity to find.</typeparam>
        /// <returns>A IEnumerable of all Entitys of type <typeparamref name="T"/></returns>
        public ICollection<T> FindEntitiesOfType<T>() where T : Entity
        {
            List<T> returnEnts = new List<T>();
            foreach (Entity e in entities)
            {
                T eAsT = e as T;
                if (eAsT != null)
                    returnEnts.Add(eAsT);
            }
            return returnEnts.ToArray();
        }

        /// <summary>
        /// Request a specific component of an <see cref="Entity"/>
        /// </summary>
        /// <typeparam name="T">The type of the component.</typeparam>
        /// <param name="entity">The <see cref="Entity"/> to search</param>
        /// <returns>The component instance, or null if it could not be found.</returns>
        public T RequestComponent<T>(Entity entity) where T : BaseComponent
        {
            if(subsystems.ContainsKey(typeof(T)))
                return (T)subsystems[typeof(T)].FindComponentForEntity(entity);
            else
                return null;
        }
        
        #endregion

        #region Task Implementation

        /// <summary>
        /// Execute the world when it is ran in the Kernel. This makes sure that all Entitys that are pending
        /// addition/removal are dealt with accordingly, and that all subsystems have chance to execute.
        /// </summary>
        public override void Execute(double elapsedTime)
        {
            UpdateEntityList();

            foreach (ISubsystem subsystem in subsystems.Values)
                subsystem.Tick(elapsedTime);
        }

        #endregion
        #region Backend Implementation

        void DoAddEntity(Entity entity)
        {
            if(EntityAdded != null)
            {
                EntityAddedEventArgs args = new EntityAddedEventArgs();
                args.Entity = entity;
                EntityAdded(this, args);
            }

            entity.Initialize(resourceFactory);
            entities.Add(entity);
        }

        void DoRemoveEntity(Entity entity)
        {
            entities.Remove(entity);

            if (EntityRemoved != null)
            {
                EntityRemovedEventArgs e = new EntityRemovedEventArgs();
                e.Entity = entity;
                EntityRemoved(this, e);
            }
        }

        void UpdateEntityList()
        {
            if (addedEntities.Count > 0)
            {
                addedEntities.ForEach(delegate(Entity entity) { DoAddEntity(entity); });
                addedEntities.Clear();
            }

            if (removedEntities.Count > 0)
            {
                removedEntities.ForEach(delegate(Entity entity) { DoRemoveEntity(entity); });
                removedEntities.Clear();
            }
        }

        #endregion

    }
}